package calculator;

class Input
{
	public Input(String in) {value = in;}
	String value;
	int length() { return value.length();}
	char charAt(int idx) { return value.charAt(idx);}
	String substring(int idx) {return value.substring(idx);}
	public void set(String val) { value = val;}
	@Override
	public String toString() { return value;}
}

public class Parser {

	static int nestedParenthesis = 0;
	
	static IExpression plus(IExpression left, Input input)
	{
		assert(input.charAt(0) == '+');
		
		if (left == null)
		{
			input.set(input.substring(1));
			return new PositiveExpression(Parser.parse(input, true));
		}

		input.set(input.substring(1));
		return new PlusExpression(left, Parser.parse(input));
	}

	static IExpression minus(IExpression left, Input input)
	{
		assert(input.charAt(0) == '-');

		if (left == null)
		{
			input.set(input.substring(1));
			return new NegativeExpression(Parser.parse(input, true));
		}
		
		input.set(input.substring(1));
		return new MinusExpression(left, Parser.parse(input, true));
	}

	static IExpression multiplicate(IExpression left, Input input)
	{
		assert(input.charAt(0) == '*');

		if (left == null)
			return new ErrorExpression("* without left hand operand");
		
		input.set(input.substring(1));
		return new MultiplicationExpression(left, Parser.parse(input));
	}

	static IExpression divide(IExpression left, Input input)
	{
		assert(input.charAt(0) == '/');

		if (left == null)
			return new ErrorExpression("/ without left hand operand");

		input.set(input.substring(1));
		return new DivisionExpression(left, Parser.parse(input, true));
	}

	static IExpression parenthesis(Input input) {
		try
		{
			assert(input.charAt(0) == '(');
			Parser.setParenthesis(true);
			input.set(input.substring(1));
			IExpression result = new ParenthesisExpression(Parser.parse(input));
			if (input.charAt(0) != ')')
			{
				Parser.setParenthesis(false);
				return new ErrorExpression("Could not find )");
			}
			input.set(input.substring(1));
			Parser.setParenthesis(false);
			return result;
		}
		catch (StringIndexOutOfBoundsException ex)
		{
			return new ErrorExpression("Missing a )");
		}
	}

	private static boolean isParenthesis()
	{
		return Parser.nestedParenthesis > 0;
	}
	
	private static void setParenthesis(boolean b) {
		if (b)
		{
			++Parser.nestedParenthesis;
		}
		else
		{
			--Parser.nestedParenthesis;
		}
	}

	static IExpression basic(Input input)
	{
		StringBuilder number = new StringBuilder();
		boolean hasPeriod = false;

		for (int index = 0; index < input.length(); ++index)
		{
			char c = input.charAt(index);
			if ((c >= '0' && c <= '9') || c == '.')
			{
				if (c == '.' && hasPeriod)
				{
					input.set("");
					return new ErrorExpression("More than one period point in a number is not allowed");
				}
				else if (c == '.')
					hasPeriod = true;
				number.append(c);
			}
			else
			{
				if (number.length() > 0)
				{
					input.set(input.substring(index));
					return new Atomic(number.toString());
				}
				return new ErrorExpression("Parse Error at '" + c + "'");
			}

		}
		input.set("");
		return new Atomic(number.toString());
	}
	
	private static IExpression power(IExpression left, Input input)
	{
		assert(input.charAt(0) == '^');

		if (left == null)
			return new ErrorExpression("^ without left hand operand");
		
		input.set(input.substring(1));
		return new PowerExpression(left, Parser.parse(input, true));
	}
	
	private static IExpression parse(Input input)
	{
		return Parser.parse(input, false);
	}

	static public IExpression parse(String input)
	{
		Parser.nestedParenthesis = 0;
		return Parser.parse(new Input(input), false);
	}
	
	private static IExpression parse(Input input, boolean single)
	{
		IExpression result = null;
		
		while (input.length() > 0)
		{
			switch (input.charAt(0))
			{
			case '+':
				result = Parser.plus(result, input);
				break;
			case '-':
				result = Parser.minus(result, input);
				break;
			case '*':
				result = Parser.multiplicate(result, input);
				break;
			case '/':
				result = Parser.divide(result, input);
				break;
			case '^':
				result = Parser.power(result, input);
				break;
			case '(':
				result = Parser.parenthesis(input);
				break;
			case ')':
				if (!isParenthesis())
					return new ErrorExpression("Found ) without corresponding (");
				if (result != null)
					return result;
				return new ErrorExpression("Found ) without corresponding (");
			default:
				if (Character.isWhitespace(input.charAt(0)))
				{
					input.set(input.substring(1));
					break;
				}
				result = Parser.basic(input);
				break;
			}
			
			if (single && result != null)
				return result;
		}
		return result;
	}
}
